/** * Copyright (C) 2000 - 2009 Silverpeas * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * As a special exception to the terms and conditions of version 3.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * Open Source Software ("FLOSS") applications as described in Silverpeas's * FLOSS exception. You should have received a copy of the text describing * the FLOSS exception, and it is also available here: * "http://repository.silverpeas.com/legal/licensing" * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.silverpeas.dbbuilder; import org.silverpeas.applicationbuilder.AppBuilderException; import org.silverpeas.applicationbuilder.ApplicationBuilderItem; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.Iterator; import java.util.List; import org.jdom.Content; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.Format.TextMode; import org.jdom.output.XMLOutputter; /** * Represents an XML Document and provides convenient methods. The methods are used basically to * load a document (parse it and obtain a tree representation), save a document (save the tree * representation to a well formed XML file). More sophisticated methods are used to merge the * document with another one and to sort the tags in the document (it is needed when used in * application server). * @author Silverpeas * @version 1.0 * @since 1.0 */ public class DBXmlDocument extends ApplicationBuilderItem { public static final String ELT_MODULE = "module"; public static final String ATT_MODULE_ID = "id"; private XMLOutputter outputter = null; /** * @since 1.0 */ private Document underlyingDocument = null; /** * @since 1.0 */ private String outputEncoding = "UTF-8"; public DBXmlDocument() { } public DBXmlDocument(String directory, String name) { super(directory, name); setOutputter(); } public DBXmlDocument(File directory, String name) { super(directory, name); setOutputter(); } /** * Save the document tree in the file item * @since 1.0 * @throws AppBuilderException * @roseuid 3AAF323B0003 */ public void save() throws AppBuilderException { try { saveTo(new FileOutputStream(getPath())); } catch (FileNotFoundException fnfe) { throw new AppBuilderException("Could not save \"" + getPath().getAbsolutePath() + "\"", fnfe); } } /** * Save the document tree to a stream. This is convenient for writing in an archive * @param outStream * @throws AppBuilderException * @roseuid 3AAF41A601C1 */ public void saveTo(OutputStream outStream) throws AppBuilderException { try { getOutputter().output(getDocument(), outStream); } catch (IOException ioe) { throw new AppBuilderException("Could not save " + getName() + " to output stream", ioe); } } /** * Loads the document tree from the file system * @throws AppBuilderException * @roseuid 3AAF337D004C */ public void load() throws AppBuilderException { try { if (getPath().exists()) { loadFrom(new FileInputStream(getPath())); } else { throw new AppBuilderException("Could not find \"" + getPath().getAbsolutePath() + "\""); } } catch (MalformedURLException mue) { throw new AppBuilderException("Could not load \"" + getPath().getAbsolutePath() + "\"", mue); } catch (IOException ioe) { throw new AppBuilderException("Could not load \"" + getPath().getAbsolutePath() + "\"", ioe); } } /** * Loads the document tree from the contents of an XML file provided as a stream. This can happen * when loading from an archive. * @param xmlStream the contents of an XML file * @throws AppBuilderException * @throws IOException * @since 1.0 * @roseuid 3AAF4099035F */ public void loadFrom(InputStream xmlStream) throws AppBuilderException, IOException { // Attention à la configuration HTTP ! (Proxy : sys. props. // "http.proxy[Host|Port]) // pour accès au DOCTYPE try { SAXBuilder builder = new SAXBuilder(false); underlyingDocument = builder.build(xmlStream); } catch (JDOMException jde) { throw new AppBuilderException("Could not load \"" + getName() + "\" from input stream", jde); } } /** * Merges only the children of the root element of each document. It takes all the elements * concerned by the array of tags from all the documents to merge and adds them to the resulting * document. <strong>In the resulting document, the comments, processing instructions and entities * are removed.</strong> * @param tagsToMerge * @param XmlFile * @throws AppBuilderException * @roseuid 3AAF3793006E */ public void mergeWith(String[] tagsToMerge, DBXmlDocument XmlFile) throws AppBuilderException { /** * gets the resulting document from the master document. Cloning the document is important. If * you clone or copy an element, the copy keeps his owner and, as a result, the element appears * twice in the document */ Element root = getDocument().getRootElement(); root.detach(); /** merges the elements in the resulting document */ /** gets the root element of the documents to merge (excluding master) */ Document documentToBeMerged = (Document) XmlFile.getDocument().clone(); Element tempRoot = documentToBeMerged.getRootElement(); /** gets all the elements which will be included in the resulting document */ for (int iTag = 0; iTag < tagsToMerge.length; iTag++) { for (Object child : tempRoot.getChildren(tagsToMerge[iTag])) { if (child instanceof Content) { Content newElement = (Content) ((Content) child).clone(); newElement.detach(); root.addContent(newElement); } } } /** the result */ setDocument(new Document(root)); } // mergeWith /** * Sorts the children elements of the document root according to the array order. The tags not * found in the array remain in the same order but at the beginning of the document * @param tagsToSort * @throws AppBuilderException * @roseuid 3AAF3986038D */ public void sort(String[] tagsToSort) throws AppBuilderException { /** * gets the resulting document from the master document. Cloning the document is important. If * you clone or copy an element, the copy keeps his owner and, as a result, the element appears * twice in the document */ Document resultDoc = (Document) getDocument().clone(); Element root = resultDoc.getRootElement(); /** Makes groups of elements by tag */ List eltLstLst = new ArrayList(tagsToSort.length); for (int iTag = 0; iTag < tagsToSort.length; iTag++) { List eltLst = root.getChildren(tagsToSort[iTag]); if (!eltLst.isEmpty()) { if (!root.removeChildren(tagsToSort[iTag])) { throw new AppBuilderException("Could not remove \"" + tagsToSort[iTag] + "\" elements from \"" + getName() + "\""); } } eltLstLst.add(iTag, eltLst); } /** Orders the content of the resulting document */ List allEltLst = root.getContent(); for (int iTag = 0; iTag < tagsToSort.length; iTag++) { if (!((List) eltLstLst.get(iTag)).isEmpty()) { if (!allEltLst.addAll(allEltLst.size(), (List) eltLstLst.get(iTag))) { throw new AppBuilderException("Could not add \"" + tagsToSort[iTag] + "\" elements to \"" + getName() + "\""); } } } /** the result */ underlyingDocument = resultDoc; } /** * Changes the default encoding * @param encoding the standard name of the encoding * @since 1.0 * @roseuid 3AAF4C6E027E */ public void setOutputEncoding(String encoding) { outputEncoding = encoding; setOutputter(); } private String getOutputEncoding() { return outputEncoding; } /** * @return the document tree * @since 1.0 * @roseuid 3AB0FA640395 */ public Document getDocument() { return underlyingDocument; } /** * @param doc * @since 1.0 * @roseuid 3AB0FA640395 */ public void setDocument(Document doc) { underlyingDocument = doc; } /** * Gets the size of the resulting xml document * @return the size of the document in memory, given the encoding, <code>-1</code> if unknown. * @throws AppBuilderException */ public long getDocumentSize() throws AppBuilderException { if (getDocument() != null) { long docSize; String docStr = null; docStr = getOutputter().outputString(getDocument()); docSize = docStr.length(); if (getOutputEncoding().startsWith("UTF-16")) { docSize *= 2; } return docSize; } else { return -1; } } /** * For each element in the tagsToFind arry, looks for the attribute and return its value - the * name of the element if the attribute is not found * @param tagsToFind * @param attribute * @return * @throws AppBuilderException */ public String[] getAttributeValues(String[] tagsToFind, String attribute) throws AppBuilderException { /** * gets the resulting document from the master document. Cloning the document is important. If * you clone or copy an element, the copy keeps his owner and, as a result, the element appears * twice in the document */ Document resultDoc = (Document) getDocument().clone(); Element root = resultDoc.getRootElement(); /** Makes groups of elements by tag */ List eltLstLst = new ArrayList(tagsToFind.length); for (int iTag = 0; iTag < tagsToFind.length; iTag++) { List eltLst = root.getChildren(tagsToFind[iTag]); if (!eltLst.isEmpty()) { if (!root.removeChildren(tagsToFind[iTag])) { throw new AppBuilderException("Could not remove \"" + tagsToFind[iTag] + "\" elements from \"" + getName() + "\""); } } eltLstLst.add(iTag, eltLst); } if (eltLstLst.isEmpty()) { return null; } String[] attributeValues = new String[eltLstLst.size()]; for (int i = 0; i < eltLstLst.size(); i++) { List eltLst = (List) eltLstLst.get(i); for (int j = 0; j < eltLst.size(); j++) { Element e = (Element) eltLst.get(j); if (attribute != null) { attributeValues[i] = e.getAttributeValue(attribute); } else { attributeValues[i] = e.getText(); } } } return attributeValues; } /** * Looks for all the elements with the given tag in the root element and its children. returns * <code>null</code> if nothing was found The values are unique in the array returned * @param tagToFind * @return the array of all the values found */ public String[] getTagValues(String tagToFind) { Set<String> result = new HashSet<String>(); if (getDocument().getRootElement().getName().equals(tagToFind)) { result.add(getDocument().getRootElement().getText()); } @SuppressWarnings("unchecked") Iterator<Element> iChildren = getDocument().getRootElement().getChildren(tagToFind).iterator(); while (iChildren.hasNext()) { result.add(iChildren.next().getText()); } if (result.isEmpty()) { return null; } return result.toArray(new String[result.size()]); } /** * Looks for all the attributes with the given name in the root element and its children. returns * <code>null</code> if nothing was found. The values are unique in the array returned * @param attributeToFind * @return the array of all the values found */ public String[] getAttributeValues(String attributeToFind) { Set<String> result = new HashSet<String>(); if (getDocument().getRootElement().getAttribute(attributeToFind) != null) { result.add(getDocument().getRootElement().getAttributeValue( attributeToFind)); } @SuppressWarnings("unchecked") Iterator<Element> iChildren = getDocument().getRootElement().getChildren().iterator(); Element currentElement = null; while (iChildren.hasNext()) { currentElement = iChildren.next(); if (currentElement.getAttribute(attributeToFind) != null) { result.add(currentElement.getAttributeValue(attributeToFind)); } } if (result.isEmpty()) { return null; } return result.toArray(new String[result.size()]); } private XMLOutputter getOutputter() { return outputter; } private void setOutputter() { Format format = Format.getPrettyFormat(); format.setTextMode(TextMode.TRIM); format.setEncoding(outputEncoding); format.setIndent(" "); outputter = new XMLOutputter(format); } /** * Merges only the children of the root element of each document. It takes all the elements * concerned by the array of tags from all the documents to merge and adds them to the resulting * document. <strong>In the resulting document, the comments, processing instructions and entities * are removed.</strong> * @param dbbuilderItem * @param tagsToMerge * @param blocks_to_merge * @throws Exception * @roseuid 3AAF3793006E */ public void mergeWith(DBBuilderItem dbbuilderItem, String[] tagsToMerge, VersionTag[] blocks_to_merge) throws Exception { /** merges the elements in the resulting document */ Element root = getDocument().getRootElement(); root.detach(); if (blocks_to_merge == null) { System.out.println(" tagsToMerge Length=" + tagsToMerge.length); for (int iTag = 0; iTag < tagsToMerge.length; iTag++) { for (Object child : dbbuilderItem.getRoot().getChildren(tagsToMerge[iTag])) { if (child instanceof Content) { Content newElement = (Content) ((Content) child).clone(); newElement.detach(); root.addContent(newElement); } } } } else { Element moduleElement = new Element(ELT_MODULE); moduleElement.setAttribute(ATT_MODULE_ID, dbbuilderItem.getModule()); /** gets all the elements which will be included in the resulting document */ for (int iBlock = 0; iBlock < blocks_to_merge.length; iBlock++) { Element myElement = dbbuilderItem.getUniqueBlock(blocks_to_merge[iBlock].getCurrent_or_previous(), blocks_to_merge[iBlock].getVersion()); for (int iTag = 0; iTag < tagsToMerge.length; iTag++) { for (Object child : myElement.getChildren(tagsToMerge[iTag])) { if (child instanceof Content) { Content newElement = (Content) ((Content) child).clone(); newElement.detach(); moduleElement.addContent(newElement); } } } moduleElement.detach(); root.addContent(moduleElement); } } /** the result */ setDocument(new Document(root)); } // mergeWith }